﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace haptic.src.engine.threads
{
    public class JobManager
    {
        #region Constructors
        public JobManager()
        {
            m_oManagerThread = new Thread(Run);
            m_oManagerThread.Name = "JobManager";
        }
        ~JobManager()
        {
            Abort();
        }
        #endregion

        #region LifeCycle
        public void Start()
        {
            Debug.Assert(m_oManagerThread != null);
            m_oManagerThread.Start();
        }
        public void Abort()
        {
            Debug.Assert(m_oManagerThread != null);
            m_oManagerThread.Abort();
            foreach (Worker oWorker in m_oWorkers)
            {
                oWorker.Abort();
            }
        }
        #endregion

        #region Edit
        public void Add(Job oJob)
        {
            lock(this)
            {
                m_oFrameJobs.Enqueue(oJob);
            }
        }
        public void SetWorkerCount(uint uiCount)
        {
            lock(this)
            {
                m_iWorkersCountRequested = (int)uiCount;
            }
        }
        #endregion

        public void StartFrameComputing()
        {
            lock(this)
            {
                Debug.Assert(!m_bFrameStarted);
                if (m_iWorkersCountRequested < m_oWorkers.Count)
                {
                    m_oWorkers.RemoveRange(m_iWorkersCountRequested, m_oWorkers.Count - m_iWorkersCountRequested);
                    m_iWorkersCountRequested = m_oWorkers.Count;
                }
                else if (m_iWorkersCountRequested > m_oWorkers.Count)
                {
                    int iNbWorkersToAdd = m_iWorkersCountRequested - m_oWorkers.Count;
                    for (int i = 0; i < iNbWorkersToAdd; i++)
                    {
                        Worker oWorker = new Worker(this, m_oWorkers.Count);
                        m_oWorkers.Add(oWorker);
                        oWorker.Start();
                    }
                }
                m_bFrameStarted = true;
                m_oManagerGo.Set();
            }
        }
        public void EndFrameComputing()
        {
            Debug.Assert(m_bFrameStarted);
            m_oManagerFinished.WaitOne();
        }

        public void Run()
        {
            while(true)
            {
                //lock(this)
                {
                    m_oManagerGo.WaitOne();

                    // Process Jobs
                    while (true)
                    {
                        lock (m_oFrameJobs)
                        {
                            if (m_oFrameJobs.Count == 0)
                                break;
                            m_oWorkerReady.WaitOne();
                            m_oPendingJob = m_oFrameJobs.Dequeue();
                            m_oFinishedJobs.Add(m_oPendingJob);
                            m_oWorkerGo.Set();
                            m_oWorkerGotJob.WaitOne();
                        }
                    }

                    // Wait for all workers to finish
                    for (int i = 0; i < m_oWorkers.Count; i++)
                        m_oWorkerReady.WaitOne();
                    Debug.Assert(m_oFrameJobs.Count == 0);

                    // Commit
                    foreach (Job oJob in m_oFinishedJobs)
                        oJob.SendResults();
                    m_oFinishedJobs.Clear();

                    m_oManagerFinished.Set();
                }
            }
        }

        #region Attributes
        private Queue<Job> m_oFrameJobs = new Queue<Job>();
        private List<Job> m_oFinishedJobs = new List<Job>();
        private List<Worker> m_oWorkers = new List<Worker>();

        #region WorkerEvents
        public volatile EventWaitHandle m_oWorkerGo = new AutoResetEvent(false);
        public volatile EventWaitHandle m_oWorkerGotJob = new AutoResetEvent(false);
        public static volatile Semaphore m_oWorkerReady = new Semaphore(0, 20);
        #endregion

        #region ManagerEvents
        public EventWaitHandle m_oManagerGo = new AutoResetEvent(false);
        public EventWaitHandle m_oManagerFinished = new AutoResetEvent(false);
        #endregion
        public volatile Job m_oPendingJob;
        private Thread m_oManagerThread;
        private int m_iWorkersCountRequested = 0;
        private bool m_bFrameStarted = false;
        #endregion
    }
}
